package org.cable.shop.annotation.aspect;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.cable.common.constants.RedisKey;
import org.cable.common.enums.BizCodeEnum;
import org.cable.common.exception.BizException;
import org.cable.common.interceptor.LoginInterceptor;
import org.cable.common.util.CommonUtil;
import org.cable.shop.annotation.RepeatSubmit;
import org.cable.shop.annotation.RepeatSubmit.Type;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * 重复提交的切面类
 * @author hsj
 * @date 2023/11/8 10:32
 */
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {



	@Autowired
	private StringRedisTemplate redisTemplate;

	@Autowired
	private RedissonClient redissonClient;


	/**
	 * 定义pointcut表达式
	 *⽅式⼀：@annotation：当执⾏的⽅法上拥有指定的注解时⽣效（我们采⽤这）
	 *⽅式⼆：execution：⼀般⽤于指定⽅法的执⾏
	 */
	@Pointcut("@annotation(repeatSubmit)")
	public void pointCutNoRepeatSubmit(RepeatSubmit repeatSubmit){

	}

	/**
	 * 环绕通知, 围绕着⽅法执⾏
	 * @Around 可以⽤来在调⽤⼀个具体⽅法前和调⽤后来完成⼀些具体的任务。
	 *
	 *  ⽅式⼀：单⽤ @Around("execution(*net.xdclass.controller.*.*(..))")可以
	 *  ⽅式⼆：⽤@Pointcut和@Around联合注解也可以（我们采⽤这个）
	 *
	 *  两种⽅式
	 *  ⽅式⼀：加锁 固定时间内不能重复提交
	 *  ⽅式⼆：先请求获取token，这边再删除token,删除成功则是第⼀次提交
	 * @param joinPoint
	 * @param repeatSubmit
	 * @return
	 */
	@Around("pointCutNoRepeatSubmit(repeatSubmit)")
	public Object around(ProceedingJoinPoint joinPoint,RepeatSubmit repeatSubmit) throws Throwable {

		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

		Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
		//用于记录成功或者失败
        boolean res = false;
		/**
		 * 防重提交类型
		 */
		String type = repeatSubmit.limitType().name();


		if (type.equalsIgnoreCase(Type.PARAM.name())){
			//参数形式防重提交 ip+className+method+accountNo
			long lockTime = repeatSubmit.lockTime();
			//获取Ip
			String ipAddr = CommonUtil.getIpAddr(request);
			//获取注解
			MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
			Method method = methodSignature.getMethod();
			//目标类,方法
			String className = method.getDeclaringClass().getName();
			String name = method.getName();

			String key ="repeat_submit:" + CommonUtil.MD5(String.format("%s#%s#%s#%s",accountNo,ipAddr,className,name));
			log.info("key={}",key);
            //分布式锁直接设置key配置过期时间  1.当redis中已存在key,返回为false
			//res = redisTemplate.opsForValue().setIfAbsent(key, "1", lockTime, TimeUnit.SECONDS);

			//使用redisson加锁
			RLock lock = redissonClient.getLock(key);
			//尝试加锁,最多等待2秒,上锁以后5秒自动解锁
            res = lock.tryLock(2,lockTime,TimeUnit.SECONDS);

		}else {
			//令牌形式的防重提交
	     	 String requestToken = request.getHeader("request-token");
		    if (StringUtils.isBlank(requestToken)){
				throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
		    }
			/**
			 * 可以直接key是order:submit:accountNo:token,然后直接删除成功则完成,当删除key时,key不存在为false
			 */
			String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, accountNo, requestToken);
			res  =  redisTemplate.delete(key);

		}

		if(!res){
			log.error("请求订单重复提交");
			throw new BizException(BizCodeEnum.ORDER_CONFIRM_REPEAT);
		}

		log.info("环绕通知执行前");
		Object proceed = joinPoint.proceed();
		log.info("环绕通知执行后");
		return proceed;

	}




}
